#![crate_name = "gayboy"]
#![crate_type = "bin"]

use std::{sync::mpsc, thread};
use winit::{
    application::ApplicationHandler,
    event::WindowEvent,
    keyboard::{Key, NamedKey},
    platform::pump_events::{EventLoopExtPumpEvents, PumpStatus},
    window::Window,
};

use gayboy::{
    build_app_args, construct_cpu, create_window_builder, recalculate_screen, run_cpu,
    run_test_mode, set_window_size, winit_to_keypad, CpalPlayer, GBEvent, IAudioPlayer,
    RenderOptions, SCREEN_HEIGHT, SCREEN_WIDTH,
};
// deprecated WindowBuilder in winit-0.30.5
// ref link: https://docs.rs/winit/latest/winit/changelog/index.html

// the main function should be simple as well
// this main function use main_utils.rs methods
fn main() {
    let matches = build_app_args();

    let test_mode = matches.get_one::<bool>("test-mode").copied().unwrap();
    let opt_serial = matches.get_one::<bool>("serial").copied().unwrap();
    let opt_printer = matches.get_one::<bool>("printer").copied().unwrap();
    let opt_classic = matches.get_one::<bool>("classic").copied().unwrap();
    let opt_audio = matches.get_one::<bool>("audio").copied().unwrap();
    let opt_skip_checksum = matches.get_one::<bool>("skip-checksum").copied().unwrap();
    let filename = matches.get_one::<String>("filename").unwrap();
    let scale = matches.get_one::<u32>("scale").copied().unwrap_or(2);

    if test_mode {
        run_test_mode(filename, opt_classic, opt_skip_checksum);
    }

    let cpu = construct_cpu(
        filename,
        opt_classic,
        opt_serial,
        opt_printer,
        opt_skip_checksum,
    );

    if cpu.is_none() {
        std::process::exit(-1)
    }

    let mut cpu = cpu.unwrap();

    let mut cpal_audio_stream = None;
    if opt_audio {
        let player = CpalPlayer::get();
        match player {
            Some((v, s)) => {
                cpu.enable_audio(Box::new(v) as Box<dyn IAudioPlayer>);
                cpal_audio_stream = Some(s);
            }
            None => {
                eprintln!("{}", "Could not open audio device}");
                std::process::exit(-1);
            }
        }
    }

    let romname = cpu.romname();

    let (sender1, receiver1) = mpsc::channel();
    let (sender2, receiver2) = mpsc::sync_channel(1);

    let mut event_loop = winit::event_loop::EventLoop::new().unwrap();
    let window_builder = create_window_builder(&romname);
    // TODO: create a Window wrapper

    let (window, display) = glium::backend::glutin::SimpleWindowBuilder::new()
        .set_window_builder(window_builder)
        .build(&event_loop);
    set_window_size(&window, scale);

    let mut texture = glium::texture::texture2d::Texture2d::empty_with_format(
        &display,
        glium::texture::UncompressedFloatFormat::U8U8U8,
        glium::texture::MipmapsOption::NoMipmap,
        SCREEN_WIDTH as u32,
        SCREEN_HEIGHT as u32,
    )
    .unwrap();

    let mut renderoptions = <RenderOptions as Default>::default();

    let cputhread = thread::spawn(move || run_cpu(cpu, sender2, receiver1));

    event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
    'evloop: loop {
        let timeout = Some(std::time::Duration::ZERO);
        /*
                  fn pump_app_events<A: ApplicationHandler<Self::UserEvent>>(
                &mut self,
                timeout: Option<Duration>,
                app: &mut A,
            ) -> PumpStatus {
                #[allow(deprecated)]
                self.pump_events(timeout, |event, event_loop| {
                    event_loop::dispatch_event_for_app(app, event_loop, event)
                })
            }

              fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
        where
            F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
        {
            self.event_loop.pump_events(timeout, event_handler)
        }
                 */

        let mut app = WinitApp::default();

        // let status = event_loop.pump_events(timeout, |ev, elwt| {
        //     use winit::event::ElementState::{Pressed, Released};
        //     use winit::event::{Event, WindowEvent};
        //     use winit::keyboard::{Key, NamedKey};

        //     match ev {
        //         Event::WindowEvent { event, .. } => match event {
        //             WindowEvent::CloseRequested => elwt.exit(),
        //             WindowEvent::KeyboardInput {
        //                 event: keyevent, ..
        //             } => match (keyevent.state, keyevent.logical_key.as_ref()) {
        //                 (Pressed, Key::Named(NamedKey::Escape)) => elwt.exit(),
        //                 (Pressed, Key::Character("1")) => set_window_size(&window, 1),
        //                 (Pressed, Key::Character("r" | "R")) => set_window_size(&window, scale),
        //                 (Pressed, Key::Named(NamedKey::Shift)) => {
        //                     let _ = sender1.send(GBEvent::SpeedUp);
        //                 }
        //                 (Released, Key::Named(NamedKey::Shift)) => {
        //                     let _ = sender1.send(GBEvent::SpeedDown);
        //                 }
        //                 (Pressed, Key::Character("t" | "T")) => {
        //                     renderoptions.linear_interpolation =
        //                         !renderoptions.linear_interpolation;
        //                 }
        //                 (Pressed, winitkey) => {
        //                     if let Some(key) = winit_to_keypad(winitkey) {
        //                         let _ = sender1.send(GBEvent::KeyDown(key));
        //                     }
        //                 }
        //                 (Released, winitkey) => {
        //                     if let Some(key) = winit_to_keypad(winitkey) {
        //                         let _ = sender1.send(GBEvent::KeyUp(key));
        //                     }
        //                 }
        //             },
        //             _ => (),
        //         },
        //         _ => (),
        //     }
        // });

        let status = event_loop.pump_app_events(timeout, &mut app);

        if let PumpStatus::Exit(_) = status {
            break 'evloop;
        }
        match receiver2.recv() {
            Ok(data) => recalculate_screen(&display, &mut texture, &*data, &renderoptions),
            Err(..) => break 'evloop, // Remote end has hung-up
        }
    }

    drop(cpal_audio_stream);
    drop(receiver2); // Stop CPU thread by disconnecting
    let _ = cputhread.join();
}

// TODO: ???
// ref link: https://docs.rs/winit/latest/src/pump_events/pump_events.rs.html#62

#[derive(Default)]
struct WinitApp {
    window: Option<Window>,
}

impl ApplicationHandler for WinitApp {
    fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
        let window_attr = Window::default_attributes().with_title("WinitApp");
        self.window = Some(event_loop.create_window(window_attr).unwrap());
    }

    fn window_event(
        &mut self,
        event_loop: &winit::event_loop::ActiveEventLoop,
        window_id: winit::window::WindowId,
        event: winit::event::WindowEvent,
    ) {
        use winit::event::ElementState::{Pressed, Released};

        println!("{event:?}");
        let (sender1, receiver1) = mpsc::channel();
        // let (sender2, receiver2) = mpsc::sync_channel(1);
        let mut renderoptions = <RenderOptions as Default>::default();

        let window = match self.window.as_ref() {
            Some(window) => window,
            None => return,
        };

        match event {
            WindowEvent::CloseRequested => event_loop.exit(),
            WindowEvent::KeyboardInput {
                event: keyevent, ..
            } => match (keyevent.state, keyevent.logical_key.as_ref()) {
                (Pressed, Key::Named(NamedKey::Escape)) => event_loop.exit(),
                (Pressed, Key::Character("1")) => set_window_size(&window, 1),
                (Pressed, Key::Character("r" | "R")) => set_window_size(&window, 2u32),
                (Pressed, Key::Named(NamedKey::Shift)) => {
                    let _ = sender1.send(GBEvent::SpeedUp);
                }
                (Released, Key::Named(NamedKey::Shift)) => {
                    let _ = sender1.send(GBEvent::SpeedDown);
                }
                (Pressed, Key::Character("t" | "T")) => {
                    renderoptions.linear_interpolation = !renderoptions.linear_interpolation;
                }
                (Pressed, winitkey) => {
                    if let Some(key) = winit_to_keypad(winitkey) {
                        let _ = sender1.send(GBEvent::KeyDown(key));
                    }
                }
                (Released, winitkey) => {
                    if let Some(key) = winit_to_keypad(winitkey) {
                        let _ = sender1.send(GBEvent::KeyUp(key));
                    }
                }
            },
            _ => (),
        }
    }
}
